import pandas as pd
import numpy as np
from pulp import LpMaximize, LpProblem, LpVariable, lpSum, value
from skopt import gp_minimize
from skopt.space import Real
from skopt.utils import use_named_args


# 生成随机数据
def generate_random_data(num_applicants, num_positions):
    applicants_data = {
        '应聘者': [f'{i + 1}' for i in range(num_applicants)],
        '笔试成绩': np.random.randint(270, 300, num_applicants).tolist(),
        '知识面评分': np.random.randint(1, 5, num_applicants).tolist(),
        '理解能力评分': np.random.randint(1, 5, num_applicants).tolist(),
        '应变能力评分': np.random.randint(1, 5, num_applicants).tolist(),
        '表达能力评分': np.random.randint(1, 5, num_applicants).tolist(),
        '意愿1': [f'部门{np.random.randint(1, num_positions + 1)}' for _ in range(num_applicants)],
        '意愿2': [f'部门{np.random.randint(1, num_positions + 1)}' for _ in range(num_applicants)]
    }

    positions_data = {
        '用人部门': [f'部门{j + 1}' for j in range(num_positions)],
        '知识面要求': np.random.randint(1, 5, num_positions).tolist(),
        '理解能力要求': np.random.randint(1, 5, num_positions).tolist(),
        '应变能力要求': np.random.randint(1, 5, num_positions).tolist(),
        '表达能力要求': np.random.randint(1, 5, num_positions).tolist(),
        '福利待遇': np.random.randint(1, 5, num_positions).tolist(),
        '工作条件': np.random.randint(1, 5, num_positions).tolist(),
        '劳动强度': np.random.randint(1, 5, num_positions).tolist(),
        '晋升机会': np.random.randint(1, 5, num_positions).tolist(),
        '深造机会': np.random.randint(1, 5, num_positions).tolist()
    }

    return pd.DataFrame(applicants_data), pd.DataFrame(positions_data)


# 计算匹配分数
def calculate_match(applicant, position, alpha=0.5, beta=0.1, gamma=0.2):
    exam_score = (applicant['笔试成绩'] - min(df_applicants['笔试成绩'])) / (
                max(df_applicants['笔试成绩']) - min(df_applicants['笔试成绩']))
    exam_score *= 1 + 2 * exam_score
    match = (
            alpha * exam_score -
            (1 - alpha) * (
                    abs(applicant['知识面评分'] - position['知识面要求']) +
                    abs(applicant['理解能力评分'] - position['理解能力要求']) +
                    abs(applicant['应变能力评分'] - position['应变能力要求']) +
                    abs(applicant['表达能力评分'] - position['表达能力要求'])
            )
    )
    if position.name in [applicant['意愿1'], applicant['意愿2']]:
        match += beta * 100
    match += gamma * (
            position['福利待遇'] +
            position['工作条件'] +
            position['劳动强度'] +
            position['晋升机会'] +
            position['深造机会']
    )
    return match


# 创建匹配矩阵
def create_match_matrix(df_applicants, df_positions, alpha, beta, gamma):
    match_matrix = []
    for _, applicant in df_applicants.iterrows():
        matches = []
        for _, position in df_positions.iterrows():
            match = calculate_match(applicant, position, alpha, beta, gamma)
            matches.append(match)
        match_matrix.append(matches)
    return pd.DataFrame(match_matrix, columns=df_positions['用人部门'], index=df_applicants['应聘者'])


# 求解线性规划问题
def solve_lp_problem(df_applicants, df_positions, alpha, beta, gamma):
    df_match = create_match_matrix(df_applicants, df_positions, alpha, beta, gamma)
    prob = LpProblem("RecruitmentProblem", LpMaximize)
    x = LpVariable.dicts("x", (df_applicants['应聘者'], df_positions['用人部门']), cat='Binary')
    prob += lpSum([df_match.loc[i, j] * x[i][j] for i in df_applicants['应聘者'] for j in df_positions['用人部门']])
    for i in df_applicants['应聘者']:
        prob += lpSum([x[i][j] for j in df_positions['用人部门']]) <= 1
    for j in df_positions['用人部门']:
        prob += lpSum([x[i][j] for i in df_applicants['应聘者']]) >= 1
    prob.solve()
    total_match_score = value(prob.objective)

    # 输出分配结果
    allocation = {j: [] for j in df_positions['用人部门']}
    for i in df_applicants['应聘者']:
        for j in df_positions['用人部门']:
            if x[i][j].varValue == 1:
                allocation[j].append(i)

    return total_match_score, allocation


# 目标函数
def objective(params):
    beta, gamma = params
    beta = max(0.1, min(0.3, beta))  # 限制 beta 在 [0.1, 0.3] 范围内
    gamma = max(0.1, min(0.3, gamma))  # 限制 gamma 在 [0.1, 0.3] 范围内
    score, _ = solve_lp_problem(df_applicants, df_positions, alpha=0.5, beta=beta, gamma=gamma)
    return -score  # 贝叶斯优化是最小化目标函数，因此我们取负值来实现最大化


# 使用贝叶斯优化
def optimize_hyperparameters(df_applicants, df_positions):
    space = [
        Real(0.1, 0.3, name='beta'),
        Real(0.1, 0.3, name='gamma')
    ]

    @use_named_args(space)
    def wrapped_objective(beta, gamma):
        return objective([beta, gamma])

    res = gp_minimize(wrapped_objective, space, n_calls=50, random_state=0)

    best_beta, best_gamma = res.x
    best_score = -res.fun

    return best_beta, best_gamma, best_score


# 生成数据
df_applicants, df_positions = generate_random_data(50, 10)

# 优化超参数
best_beta, best_gamma, best_score = optimize_hyperparameters(df_applicants, df_positions)

print("Best beta:", best_beta)
print("Best gamma:", best_gamma)
print("Best score:", best_score)

# 使用最优参数解决大规模数据集问题
df_applicants_large, df_positions_large = generate_random_data(4000, 20)
large_scale_score, large_scale_allocation = solve_lp_problem(df_applicants_large, df_positions_large, alpha=0.5,
                                                             beta=best_beta, gamma=best_gamma)

print("\nLarge scale score:", large_scale_score)
print("\nLarge scale allocation result:")
for dept, applicants in large_scale_allocation.items():
    print(f"{dept}: {', '.join(applicants)}")
